package club.janna.rpcdemo.client;

import club.janna.rpcdemo.client.discover.ServiceDiscover;
import club.janna.rpcdemo.client.invoke.NetInvoker;
import club.janna.rpcdemo.domain.Request;
import club.janna.rpcdemo.domain.Response;
import club.janna.rpcdemo.domain.ServiceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.remoting.RemoteTimeoutException;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class RPCProxyFactory {

    @Autowired
    private NetInvoker netInvoker;
    @Autowired
    private ServiceDiscover serviceDiscover;

    public <T> T getBean(Class<T> tClass) {
        return (T) Proxy.newProxyInstance(tClass.getClassLoader(),
                    new Class[]{tClass},
                    new RPCInvocationHandler(tClass, serviceDiscover, netInvoker));
    }

    @Slf4j
    private static class RPCInvocationHandler implements InvocationHandler {

        private Class<?> aClass;

        private ServiceDiscover serviceDiscover;
        private NetInvoker netInvoker;

        public RPCInvocationHandler(Class<?> aClass, ServiceDiscover serviceDiscover, NetInvoker netInvoker) {
            this.aClass = aClass;
            this.serviceDiscover = serviceDiscover;
            this.netInvoker = netInvoker;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            String serviceName = aClass.getName();
            log.debug("调用服务：{}, 方法：{}", serviceName, method);

            // 如果是从Object中定义的方法，如hashCode，wait等，直接调用代理类的方法
            if(Object.class.equals(method.getDeclaringClass()))
                return method.invoke(proxy, args);

            ServiceInfo serviceInfo = serviceDiscover.getServiceInfo(serviceName);
            if(serviceInfo == null)
                throw new RuntimeException("未找到服务，serviceName -> " + serviceName);

            Request request = Request.builder()
                    .serviceName(serviceName)
                    .method(methodName)
                    .parameterTypes(method.getParameterTypes())
                    .args(args)
                    .build();

            log.debug("request -> {}", request);

            Response response = netInvoker.invoke(serviceInfo, request);
            if(response == null)
                throw new RuntimeException("调用远程系统出错，response is null");

            switch (response.getState()) {
                case ERROR:
                    throw new RuntimeException("远程系统执行出现异常", response.getError());
                case TIMEOUT:
                    throw new RemoteTimeoutException("调用远程系统超时");
                case NOT_FOUND:
                    throw new RuntimeException("远程系统上未找到该服务, serviceName -> " + serviceName);
            }
            return response.getResult();
        }
    }
}
